﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SequenceAssembler
{
	class SimpleCellMerger
	{
		public BackGraph bg { get; private set; }
		public SimpleCellMerger(BackGraph backGraph) {
			bg = backGraph;
		}

		public void Merge() {
			long before = bg.CellCount;
			long after = 0;
			while (before != after)
			{
				Console.WriteLine("Pre merge cell count: {0}", bg.CellCount);
				before = bg.CellCount;
				MergeOnce();
				after = bg.CellCount;
				Console.WriteLine("Post merge cell count: {0}", bg.CellCount);
			}
		}

		/// <summary>
		/// Merge cells with only one link between them
		/// Do not merge twins
		/// Do merge compatible ReadInfo
		/// </summary>
		private void MergeOnce() {
			List<long> celllist = new List<long>(bg.CellList);
			foreach (long id in celllist)
			{
				SequenceCell cell = bg.LoadCell<SequenceCell>(id);
				if (cell == null) { continue; }
				if (cell.Arcs.Count != 1) { continue; }
				long neighId = cell.Arcs[0].Destination;
				long twinId = bg.TwinId(cell.Id);
				if (twinId == neighId) { continue; }
				SequenceCell neighCell = bg.LoadCell<SequenceCell>(neighId);
				SequenceCell twinCell = bg.LoadCell<SequenceCell>(twinId);
				//load neigh's neigh's twin cell for correcting pointer to twin cell later
				long[] preTwinIds = new long[neighCell.Arcs.Count];
				for (int i = 0; i < preTwinIds.Length; ++i) {
					preTwinIds[i] = bg.TwinId(neighCell.Arcs[i].Destination);
				}
				SequenceCell[] preTwinCells = bg.LoadCells<SequenceCell>(preTwinIds);

				long twinNeighId = bg.TwinId(neighCell.Id);
				SequenceCell twinNeighCell = bg.LoadCell<SequenceCell>(twinNeighId);
				if (twinNeighCell.Arcs.Count != 1) { continue; }

				bg.DeleteCell(neighCell);
				bg.DeleteCell(twinNeighCell);
				SequenceCell newCell = MergeTwoCell(cell, neighCell, id);
				SequenceCell newTwinCell = MergeTwoCell(twinNeighCell, twinCell, twinId, twinNeighId, preTwinCells);
				bg.SaveCell(newCell);
				bg.SaveCell(newTwinCell);
				bg.SaveCells(preTwinCells);
			}
		}

		private SequenceCell MergeTwoCell(SequenceCell cell, SequenceCell neighCell, long id, long idToReplace = 0L, SequenceCell[] cellsToCorrect = null)
		{
			List<ReadOffset> newReadInfo = new List<ReadOffset>();
			int i = 0, j = 0;
			while (i < cell.ReadInfo.Count && j < neighCell.ReadInfo.Count) {
				if (cell.ReadInfo[i].ReadId < neighCell.ReadInfo[j].ReadId) {
					newReadInfo.Add(cell.ReadInfo[i++]);
				} else if (cell.ReadInfo[i].ReadId < neighCell.ReadInfo[j].ReadId) {
					++(neighCell.ReadInfo[j].Offset);
					newReadInfo.Add(neighCell.ReadInfo[j++]);
				} else { // readId compatible
					if (cell.ReadInfo[i].Offset < neighCell.ReadInfo[j].Offset + 1) {
						newReadInfo.Add(cell.ReadInfo[i++]);
					}
					else if (cell.ReadInfo[i].Offset > neighCell.ReadInfo[j].Offset + 1)
					{
						++(neighCell.ReadInfo[j].Offset);
						newReadInfo.Add(neighCell.ReadInfo[j++]);
					} else { // offset compatible
						newReadInfo.Add(cell.ReadInfo[i++]);
						++j;
					}
				}
			}
			cell.ReadInfo = newReadInfo;
			cell.Arcs = neighCell.Arcs;
			cell.Coverage = (cell.Coverage * cell.Size + neighCell.Coverage * neighCell.Size) / (cell.Size + neighCell.Size);
			cell.Data.push_back_all(neighCell.Data);
			cell.Id = id;

			if (cellsToCorrect != null && cellsToCorrect.Length != 0) {
				foreach (var toCorrect in cellsToCorrect)
				{
					foreach (var arc in toCorrect.Arcs) {
						if (arc.Destination == idToReplace) {
							arc.Destination = id;
						}
					}
				}
			}

			return cell;
		}
	}
}
